Repository navigation
Add format-selection decision framework page and case studies - #205
Conversation
Adds a two-page decision framework that takes a reader from "I have
geospatial data to publish or migrate" to a short list of cloud-optimized
format candidates and the layout choices that make each one work.
decision-framework/index.qmd
- Preconditions P1 (partial/parallel reads over HTTP) and P2 (no opaque
bundles), written on top of the guide's existing cloud-optimized
format pattern and glossary rather than as a parallel definition.
- The spine: a control fork (existing data, reprocessable) then a
topology gate that opens a candidate set, with a Mermaid flowchart.
- Refinements: intent, the object-size / grouping lever, ecosystem
maturity, and interpretation encoding.
- Cross-cutting notes: metadata and cataloging (STAC recommended, not
prescribed), within-format depth, and dead ends framed as "unlock
this".
- A short optional section on making the choice quantitative, linking
out rather than reproducing the full spec.
decision-framework/case-studies.qmd
- Six products run through the spine, five backed by measurements,
leading with the measured outcome and the recurring lesson that
cloud-optimized is rarely smaller and the gap is usually the codec.
Also registers both pages in _quarto.yml (placement flagged for review),
adds them to the Welcome table of contents, and credits CNES and the
Copernicus / NASA source missions in _thankyous.qmd.
Refs #183
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The optional scoring section named two measured criteria (size and performance) but had no outbound link, so a reader had no way to reproduce a number. Point both pages at the open-source harness the case-study measurements actually came from. - Framework page: a "where the measured numbers come from" paragraph after the criteria table, naming what the harness measures (object-size distribution and tier fitness, write, range-request-aware read, tile display), that datasets are described in config files rather than baked in, and linking the docs and configuration guide. - Case studies: provenance in the measurement caveat, plus a note in the acknowledgements that the harness is maintained outside this guide so its methodological choices stay debatable on their own terms (ADR 0002). Refs #183 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The recurring-lesson callout paired the "rarely smaller" headline with a measurement-hygiene warning. Replace it with the mechanism that actually explains the gap: overviews, levels of detail and spatial indexes are structures the source never carried, so they add volume, and that volume is the feature being bought. Anchored to the two cases that show it (the COG carrying overviews, the GeoParquet row-group index). The like-for-like-on-content caveat is dropped from the summary only. It stays inline in cases 3 and 5, where it is concrete and actionable. Also aligns the same paragraph on the framework page. Refs #183 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
This first cut does not include the "quantitative harness" enabled by https://github.com/developmentseed/cng-formats-benchmark but mention the possible extension. |
… split The maturity refinement was a three-row table with no definitions, which made it the weakest of the four axes. Replace it with the substance from the strategic-report state-of-the-art work: - An explicit L1-L5 scale, so the judgement is stated rather than impressionistic, and a per-format table giving specification status, tooling and level. This also separates FlatGeobuf (L3) from GeoParquet and COPC (L4), which the old table lumped together. - The distinction that is easiest to get wrong: Zarr v3 the substrate is dependable (core spec final in 2024, sharding stable in zarr-python 3.x and readable by GDAL and xarray), while GeoZarr the convention layer is still being ratified in an OGC SWG. The L2 belongs to the conventions, not to Zarr. - "Maturity is a warning, not a veto", which reconciles the topology gate offering GeoZarr as the primary N-D cube candidate with that candidate sitting at L2: there is no mature alternative at that topology, so a low level means plan for change, pin versions, and expect the tooling to improve. Points at geozarr.org for current status rather than quoting a ratification date that will age badly. Refs #183 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…y in decision framework section on Zarr v3
…ies and updating case study references for improved context and accuracy.
gadomski
left a comment
There was a problem hiding this comment.
I did a run-through of the first half of index.qmd; I'll try to come back for the rest later when I get another free time window. I think the high level flowchart and framework feels about correct, but we need to be sure to explain (or link to definition) of terms before we use them — as an "insider" I still wondered what some acronyms or phrases meant.
Co-authored-by: Pete Gadomski <pete.gadomski@gmail.com>
Co-authored-by: Pete Gadomski <pete.gadomski@gmail.com>
Co-authored-by: Pete Gadomski <pete.gadomski@gmail.com>
…mat selection and enhancing the explanation of topology and control stages.
…ifying definitions for improved understanding and context.
…n for improved accuracy and understanding.
…d explanations for reprocessing and virtualization processes.
|
Thank you @gadomski for the review. I tried to integrate and improve according to your comments and suggestions. I left some opened, feel free to close them or comment further. |
…ion encoding and enhancing the explanation of the two-stage process for format selection. Update theme configuration in Quarto settings for improved clarity.
Co-authored-by: Alex I. Mandel <alex@developmentseed.org>
…minology in decision framework
|
|
||
| The lever works in both directions, and which direction you need depends on product size: | ||
|
|
||
| - **Products that are already large.** Group *within* one product. Size the shard, the tile, or the row group so it clears the policy. |
There was a problem hiding this comment.
For Zarr I think most of this holds true. However for some of the other formats, there might be a 3rd item in this list, which is to actually split an existing file into separate files because there would be too many chunks/shards in one file the index would be huge. This might be a for later discussion.
There was a problem hiding this comment.
I'm thinking about how we somewhat manually design geoparquet partitions (across files), Zarr already does this inherently. There is a a practical limit though, you wouldn't want a single COG for the whole world at 1m resolution, even if it is chunked nicely, that file would be hard to work with on a management side.
There was a problem hiding this comment.
I may be missing something but here and elsewhere it seems to be suggested that an internal COG tile could be its own object. I don't think that is the case? I modified wording above "how bytes are grouped into data chunks and chunks grouped into objects" but when we have a COG that is already too large wouldn't the solution have to be to actually break the COG into smaller regions or separate bands?
There was a problem hiding this comment.
Thx. You are right, and this is an error. For COG, COPC, and single-file GeoParquet, the file is the object. Internal tiles and row groups are byte ranges inside that one object. They do not change the object size at all. Zarr is the only format where the two coincide, because there a shard is an object.
The page also contradicted itself. The "size and shape are two different questions" paragraph below already made this distinction correctly. The table will have two columns, "what sets the object size" and "what sets the read granularity inside the object", and a sentence will state that only Zarr connects them. I will also correct the "products that are already large" bullet, which told you to size the tile or the row group.
wildintellect
left a comment
There was a problem hiding this comment.
I'm fairly happy with this as the first version. Will let the others chime in if there are still any blockers for them.
|
|
||
| Tune the lever to your [object store's](../glossary.qmd#object-storage-cloud-storage) **object-size policy** (S3, Azure Blob Storage, Google Cloud Storage, or an on-premises equivalent). Object stores degrade when they must index very many tiny objects, so most reward a larger average object. | ||
|
|
||
| One worked example: the [CNES Datalake](https://cnes.fr/en/projects/centre-de-calcul) sorts data into [tiers](../glossary.qmd#tiered-storage) by mean object size, with about 32 MB or more for warm disk and about 100 MB or more for cold tape. This serves the general point: find out whether your storage has an object-size policy, and size the lever against it. Some providers publish a threshold, some only charge you as if your objects were larger, and some have no policy at all. |
There was a problem hiding this comment.
some only charge you as if your objects were larger
can you explain this a bit more @emmanuelmathot ?
There was a problem hiding this comment.
Indeed, too vague to be useful. I meant a minimum billable object size. Several S3 storage classes charge for small objects as if they were larger. Standard-IA, One Zone-IA, and Glacier Instant Retrieval each charge a minimum of 128 KB for one object. A 10 KB object then costs the same as a 128 KB object. Glacier Flexible Retrieval and Deep Archive add 40 KB of metadata for each object.
The write does not fail, so you do not see this cost until the invoice. The text now gives this example and links to the AWS storage class table.
Will precise in the following paragraph
| This axis separates the candidates more than people expect: | ||
|
|
||
| - **Zarr v3 makes the information first-class.** With the `scale_offset` codec, a declared fill value, Zarr conventions for projection, CRS and multiscale, the transform is a first-class array property. Every reader recovers the same value. | ||
| - **COG, GeoParquet, and COPC standardize the CRS** but leave the value transform to side metadata. Readers honor side metadata unevenly. |
There was a problem hiding this comment.
I don't understand what "side metadata" means here. I think what you are saying is that these encodings are baked into the Zarr standard but not these other standards. An example may help.
There was a problem hiding this comment.
external files, aka sidecar files, somecog.xml
There was a problem hiding this comment.
can we sidecar metadata files or add this term the terminology section?
There was a problem hiding this comment.
The term is jargon. One small correction on "sidecar files": for a COG with scaled integers, the scale and the offset are usually inside the TIFF, in a GDAL metadata tag. They are not always in a separate file. The important difference is not the location. It is whether the data model of the format makes the reader apply the value.
The term appears only twice, so I removed it instead of adding it to the terminology table. The bullet will give the example you ask for.
|
|
||
| A multiscale pyramid does not have to come from one source resolution downsampled by a fixed factor. The common case is **overviews**, also called [pyramids](../glossary.qmd#overviews): downsampled copies of the same band, generated at a uniform decimation so a reader can zoom out without reading and resampling full-resolution chunks. COG overviews, COPC's octree levels of detail, and most GeoZarr pyramids work this way, and it is already covered above under [ecosystem maturity](#maturity) and the [grouping lever](#grouping-lever). | ||
|
|
||
| The same structure can also host bands that were never derived from each other and were simply captured at different native resolutions, as long as the steps between levels are chosen to land on each native resolution rather than a fixed factor. Sentinel-2 is the reference case, worked through in [case 1](case-studies.qmd) of the case studies: the 10 m, 20 m, and 60 m bands from the same sensor fold into one pyramid by aligning the decimation to each step (10 m to 20 m halves the resolution, 20 m to 60 m divides it by three), and the pyramid can continue past 60 m into ordinary overviews using the same mechanism. The [Zarr multiscales convention](https://github.com/zarr-conventions/multiscales) is built for exactly this: scale factors between levels are not required to be uniform, and its own reference examples use this Sentinel-2 layout. |
There was a problem hiding this comment.
I find this section a bit confusing (why are we talking about bands derived from one another?), perhaps we can discuss it and take a shot at making it more clear
There was a problem hiding this comment.
oh we can probably clarify that, this is probably adding too much detail about how a band is captured on some satellites. The key point is that some products have bands at different resolutions.
There was a problem hiding this comment.
This section answers @wildintellect's prevvious comment. An earlier version said mixed resolution was out of scope. We corrected that: the two cases combine into one pyramid when the step between levels is not a fixed factor. Sentinel-2 is the example, and the Zarr multiscales convention uses that same layout as its own reference example.
fair poiny though. Let's decide what we want here:
- Keep the content, and fix the confusing sentence.
- Keep a short version here, and move the Sentinel-2 detail to the Zarr page or to the case study.
Adding this point to the call. Leaving this open.
There was a problem hiding this comment.
Pete: "scale factors between levels are not required to be uniform", however if you need to combine bands or variables they need to be the same resolution to avoid resampling
There was a problem hiding this comment.
todo: verify that COGs allow for arbitrary overview resolutions
There was a problem hiding this comment.
https://gdal.org/en/stable/programs/gdal_raster_overview_refresh.html#cmdoption-gdal_raster_overview_refresh-levels 👈🏼 looks like a good place to start to proof this out.
|
|
||
| Frame the branch that way, and route the reader back to the options: reprocess if you can get the chain changed, virtualize if a reader exists, or convert on ingest. Say what is missing, so somebody can unlock it. | ||
|
|
||
| ## Optional: make it quantitative |
There was a problem hiding this comment.
I wonder if we can simplify this section with just a few sentences and pointing readers to the cng-formats-benchmark repo.
There was a problem hiding this comment.
Agreed. The section is probably too long here. I will reduce it for this release. However, I would like to defer a future work for a potential dedicated page to describe how to make a quantitative assessement using the benchmark and how to integrate in the decision framework. Followup issue opened here
| shape. A *family*, not a format. Two or three candidates normally come through, | ||
| and the refinements below rank them. | ||
|
|
||
| The hand-off is worth stating outright, because the diagram can be read as if |
There was a problem hiding this comment.
I don't understand what "handoff" is being referred to here
There was a problem hiding this comment.
The framework runs in two stages, and the topology question marks the hand-off between them.
This made more sense to me before, will think about how to be more clear.
There was a problem hiding this comment.
I introduced "hand-off" to replace "hinge", because I thought it was the clearer word but seems not. I will describe the transition literally.
|
|
||
| The temporal benefit appears only once many scenes are concatenated along time into a single cube. That is the regime measured in [case 2](#case-time-cube), and it is a collection-level layout decision, not something a scene-size store can give you. Read the per-scene store as a building block and the cube as the product. | ||
|
|
||
| The associated masking product, which is written as many small GeoTIFFs, is a clear anti-pattern to fix at the source. |
There was a problem hiding this comment.
This feels out of context and why its a clear anti-pattern is not really explained, so we should probably just remove this sentence.
There was a problem hiding this comment.
You are right that the sentence was not very clear. I would prefer to fix it than to delete it, because the finding is real and it connects to the object-size lever.
|
|
||
| Note that this compares GeoParquet against the **source**, not against FlatGeobuf. Against its actual rival in the candidate set, GeoParquet is the smaller of the two, because FlatGeobuf cannot compress at all. | ||
|
|
||
| Each pass sits below the warm-tier floor. The lever is therefore **aggregation across passes**: concatenate first, then size the row groups. A one-for-one transcode of each pass would leave the object-size problem in place. The gain over the shapefile is the bounding-box row-group index, not the size. |
There was a problem hiding this comment.
This is a bit hard to digest without knowing what passes are and more about the data, but maybe that is unavoidable in order to provide a succinct case study. Are there multiple passes per shapefile or is it one per shapefile? Does each pass constitute one Geoparquet row or many?
There was a problem hiding this comment.
Fair point. One pass is one overflight, and the mission delivers one shapefile per pass. But LakeSP Prior reports the whole prior lake database on every pass, not only the lakes that pass observed, so about 78 % of the rows describe a lake with no observation and carry a NULL geometry. Many rows per pass, and most of them empty. The GeoParquet output keeps the same rows. That fact also drives the aggregation lever: concatenating passes stacks these row sets, and the row groups are sized after that.
I had this wrong in the text and will correct it
|
|
||
| This case is the counterpart to cases 1 and 2. There, one large band already clears the tier, so you group *within* a product. Here the products are small, so you group *above* a single product. | ||
|
|
||
| ## 6. Existing archive to a virtual store |
There was a problem hiding this comment.
We should add an actual example from NASA here, we have many now.
| 2. **What the benchmark measurements showed.** | ||
|
|
||
| The examples use public Copernicus and NASA missions. The [CNES Datalake](https://cnes.fr/en/projects/centre-de-calcul) appears as one worked example of a [tiered object store](../glossary.qmd#tiered-storage). Its two thresholds appear throughout the cases below: a **warm tier** with a mean-object-size floor of about 32 MB on disk, and a **cold tier** of about 100 MB on tape. It is not the only kind of object store, and its tier numbers are not a general rule. | ||
|
|
There was a problem hiding this comment.
How do these thresholds work -- is the mean object size per bucket or some other grouping unit? And you don't have a choice, if you have mean objects of 32 MB or smaller, all those objects are in warm storage and if you have mean object size of greater than 32 MB its in cold storage? Or just greater than 100MB? Then what happens inbetween 32 and 100 MB?
There was a problem hiding this comment.
Good question. I realise our wording cause the confusion. "Sorts data into tiers" makes the process sound automatic and it is not the case.
The Datalake is an in-house S3 service with a lifecycle policy that moves data between disk and tape. Each tier has a recommended minimum mean object size: about 32 MB for disk, and about 100 MB for tape. The check is mechanical. The system divides the total volume by the object count, then compares the result to the floor for the tier.
- A larger mean object size makes your data eligible for more tiers. It never moves your data to a colder tier.
- Between 32 MB and 100 MB, the data is suitable for disk but not for tape.
Below 32 MB is the failure case. The penalty is not the tier. Too many small objects fill the object index table before the disk capacity is full. The Datalake then cannot supply its pooled volume to any project, even when much disk space remains. - That last point is the real motivation, and it is much stronger than "tiers", so I will rewrite the paragraph around it. In CNES, floors are advisory, not enforced.
|
|
||
| The correct direction depends on the size of your product: | ||
|
|
||
| - **Products that are already large.** For Zarr, set the shard size so that the mean object size clears the policy. For a single-file format, the file is already one object. To make the objects smaller, split the file by region, by band, or by time step. |
There was a problem hiding this comment.
We haven't solidly defined products. This only makes sense when there is an existing "product" we are converting from, which we've already stated isn't the case. I think this reads fine without these bullets at all, but if we decide to keep them we should make clear these statements only make sense in the scenario of an existing product we are converting.
First contribution toward the decision framework (#183). Opened against the
feat/decision-frameworkfeatureThe spine
flowchart TD A([Data to publish or migrate]) --> B{Existing data?} B -- No --> C{Archival-format<br/>mandate?} C -- Yes --> C1[Optimize the mandated format<br/>+ publish a cloud-optimized copy] C -- No --> T{{What shape are the data?}} B -- Yes --> D{Can you<br/>reprocess?} D -- Yes --> T D -- No --> P{Can you repackage<br/>the delivered files?} P -- Yes --> P1[Format free,<br/>content fixed as delivered] P1 --> T P -- No --> E{Virtualizable?} E -- Yes --> E1[VirtualiZarr / Kerchunk / Icechunk<br/>limited, better than nothing] E -- No --> E2[Not yet possible<br/>say what is missing] T --> T1(2D raster<br/>COG, Zarr, PMTiles) T --> T2(N-D cubes<br/>GeoZarr, COG, CO-HDF5) T --> T3(Point cloud<br/>COPC, GeoParquet) T --> T4(Vector / tabular<br/>GeoParquet, FlatGeobuf, PMTiles) T1 --> R([Refine: intent, object-size lever,<br/>maturity, interpretation]) T2 --> R T3 --> R T4 --> R classDef entry fill:#e7eefb,stroke:#3f6db3,stroke-width:2px,color:#152744 classDef ask fill:#fff6dc,stroke:#ad7c00,stroke-width:2px,color:#4a3600 classDef hinge fill:#dfeffa,stroke:#2a7fb8,stroke-width:3px,color:#0e3247 classDef family fill:#e4f4ea,stroke:#39875f,stroke-width:2px,color:#12301e classDef partial fill:#fdeadb,stroke:#c06520,stroke-width:2px,color:#48260a classDef refine fill:#ece6f7,stroke:#7355a6,stroke-width:2px,color:#291d44 class A entry class B,C,D,E,P ask class T hinge class T1,T2,T3,T4 family class C1,P1,E1,E2 partial class R refineRefs #183